Skip to content

feat: Add Zarr v2 metadata access and basic auth support for OIDC-protected assets#2112

Open
cauriol wants to merge 13 commits intodevelopfrom
auth_destinee
Open

feat: Add Zarr v2 metadata access and basic auth support for OIDC-protected assets#2112
cauriol wants to merge 13 commits intodevelopfrom
auth_destinee

Conversation

@cauriol
Copy link
Copy Markdown
Collaborator

@cauriol cauriol commented Mar 25, 2026

This PR adds support for accessing Zarr v2 assets from EOProduct and extends OIDC authentication handling to support Basic authorization for protected asset access.

Changes:
-add helper methods in EOProduct to:
- extract auth headers from auth objects
- request protected assets with forwarded authentication headers
- list files from Zarr v2 metadata using .zmetadata

  • extend CodeAuthorizedAuth usage to support Basic authorization headers for protected asset access
  • protect OIDC authentication token retrieval with a lock to avoid concurrent token refresh issues

Notes
this PR currently targets Zarr v2 only through .zmetadata
Zarr v3 support can be added later if needed

@cauriol cauriol self-assigned this Mar 25, 2026
@cauriol cauriol changed the title Refactor: CodeAuthorizedAuth headers handling for header/basic auth refactor: CodeAuthorizedAuth headers handling for header/basic auth Mar 25, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 25, 2026

Test Results

    4 files  ±  0      4 suites  ±0   3m 57s ⏱️ +29s
  779 tests + 39    779 ✅ + 39  0 💤 ±0  0 ❌ ±0 
3 158 runs  +156  3 156 ✅ +156  2 💤 ±0  0 ❌ ±0 

Results for commit 9faf04a. ± Comparison against base commit dc47630.

♻️ This comment has been updated with latest results.

@eodag-bot
Copy link
Copy Markdown
Collaborator

eodag-bot commented Mar 25, 2026

badge

Code Coverage (Ubuntu)

Details
Filename                                     Stmts    Miss  Cover    Missing
-----------------------------------------  -------  ------  -------  ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
__init__.py                                      8       0  100.00%
cli.py                                         251      11  95.62%   104-115, 377, 644
config.py                                      306      24  92.16%   70-72, 75, 78, 81, 85, 89, 93-95, 587-589, 711-713, 732, 740, 770-775, 777
crunch.py                                        2       0  100.00%
api/__init__.py                                  0       0  100.00%
api/collection.py                              151       8  94.70%   177, 213, 216, 323, 362, 365, 383, 386
api/core.py                                    774      57  92.64%   263, 549, 597, 640, 680, 700, 741-746, 771, 855-874, 888, 894, 1047, 1052, 1151, 1190-1191, 1287-1288, 1314, 1345-1346, 1372, 1385, 1446-1447, 1478-1479, 1553-1558, 1570-1573, 1685, 1910, 2067, 2178, 2266-2267
api/provider.py                                382      40  89.53%   186, 190-191, 316-337, 405, 521, 525-526, 531-534, 545, 621-631, 821-822, 870, 877, 891, 914-917, 951-958, 962-963
api/search_result.py                           138      12  91.30%   111, 123, 133, 154, 208, 266, 278, 413, 468-471
api/product/__init__.py                          7       0  100.00%
api/product/_assets.py                          54       4  92.59%   97, 191, 202-206
api/product/_product.py                        344      24  93.02%   208, 364, 390-393, 430, 441-442, 551, 580, 587, 626, 797, 840-843, 852-855, 909, 975, 987, 1054
api/product/metadata_mapping.py                806      60  92.56%   125-127, 221-226, 250, 308-309, 355-356, 397, 418, 470-471, 508, 529-532, 555, 567-568, 609, 632, 657-660, 662-667, 732-737, 746, 752, 768, 776, 1010, 1163, 1185, 1194-1198, 1215-1220, 1353, 1376, 1385, 1407, 1412, 1464, 1536, 1557, 1583, 1597, 1622, 1668, 1737, 1812
api/product/drivers/__init__.py                 11       0  100.00%
api/product/drivers/base.py                     32       0  100.00%
api/product/drivers/generic.py                  11       0  100.00%
api/product/drivers/sentinel1.py                33       0  100.00%
api/product/drivers/sentinel2.py                33       0  100.00%
plugins/__init__.py                              0       0  100.00%
plugins/base.py                                 25       2  92.00%   48, 55
plugins/manager.py                             173      16  90.75%   102-107, 179, 201, 219-220, 232, 271-272, 372-375, 387-388
plugins/apis/__init__.py                         0       0  100.00%
plugins/apis/base.py                             4       0  100.00%
plugins/apis/ecmwf.py                           95       8  91.58%   163-165, 213-214, 240-242
plugins/apis/usgs.py                           182      25  86.26%   157, 263, 297, 339-341, 346, 374-375, 380, 410-417, 428-433, 455-461
plugins/authentication/__init__.py               6       1  83.33%   31
plugins/authentication/aws_auth.py             124      35  71.77%   52-54, 69-70, 142-149, 177-203, 226, 258-262, 279, 303, 319-320
plugins/authentication/base.py                  22       4  81.82%   45, 58, 81, 95
plugins/authentication/eoiam.py                 99       2  97.98%   171, 194
plugins/authentication/generic.py               16       3  81.25%   50, 55, 65
plugins/authentication/header.py                19       0  100.00%
plugins/authentication/keycloak.py              46       4  91.30%   154, 177-182
plugins/authentication/openid_connect.py       243      21  91.36%   101-102, 110-128, 175, 183, 197, 217, 363-366, 392, 433, 623-626
plugins/authentication/qsauth.py                34       1  97.06%   91
plugins/authentication/sas_auth.py              57       3  94.74%   68, 89, 135
plugins/authentication/token.py                141       9  93.62%   192, 229, 309-310, 358-362
plugins/authentication/token_exchange.py        36      14  61.11%   75, 93-121
plugins/crunch/__init__.py                       6       0  100.00%
plugins/crunch/base.py                          12       0  100.00%
plugins/crunch/filter_date.py                   59       0  100.00%
plugins/crunch/filter_latest_intersect.py       54       6  88.89%   92-93, 100-101, 103-107
plugins/crunch/filter_latest_tpl_name.py        35       0  100.00%
plugins/crunch/filter_overlap.py                66      10  84.85%   118-121, 137-163
plugins/crunch/filter_property.py               30       0  100.00%
plugins/download/__init__.py                     4       0  100.00%
plugins/download/aws.py                        402      76  81.09%   271, 305, 354-357, 387-388, 396-400, 480-483, 523-525, 529, 560-561, 567-571, 602, 667-675, 739-834, 846-851, 889, 915, 960-962, 1014
plugins/download/base.py                       288      24  91.67%   135, 165, 371-372, 416, 450, 527-531, 561, 596-597, 622-631, 690, 711, 733, 741
plugins/download/http.py                       575      74  87.13%   235, 277-280, 342-345, 348, 355-360, 391-393, 410, 425, 486, 521, 535, 549, 559-563, 579-584, 595, 614, 651-654, 675, 685, 692, 748, 857, 889, 921-930, 966, 1011-1016, 1025, 1040-1042, 1046, 1049, 1064-1065, 1075, 1150, 1202, 1244-1245, 1257, 1267, 1323-1324, 1354, 1392, 1472-1473
plugins/search/__init__.py                      25       0  100.00%
plugins/search/base.py                         197      18  90.86%   109, 113, 137-143, 218-221, 296, 317, 443, 493, 526-529, 538
plugins/search/build_search_result.py          519      91  82.47%   133-134, 170, 174, 194, 253, 423-434, 444-446, 532-538, 584, 608, 610, 677, 685-689, 710, 720, 746, 791, 816, 844, 862-877, 927, 952, 955, 959, 968, 974, 1012-1033, 1074, 1101-1102, 1111-1120, 1184, 1199, 1205, 1224-1233, 1354-1355, 1399, 1408-1410, 1467, 1515-1525
plugins/search/cop_ghsl.py                     407      85  79.12%   73-74, 109-110, 174, 176, 218, 249-252, 297, 313, 316, 350, 354-359, 374, 396, 417, 451, 467, 502, 517, 531-532, 546-548, 555, 559-561, 564-567, 585-645, 656-671
plugins/search/cop_marine.py                   273      59  78.39%   57, 65-67, 77-78, 83, 88-89, 105, 107, 110, 176-177, 220, 238, 244, 248, 252, 263, 274-275, 283, 315-318, 324, 334, 347, 351, 355, 359, 363-367, 373-376, 379-396, 413-416, 469-473, 478, 490, 504-505
plugins/search/creodias_s3.py                   29       1  96.55%   59
plugins/search/csw.py                          112      87  22.32%   99-100, 104-105, 113-170, 176-189, 197-229, 247-288
plugins/search/geodes.py                        45       0  100.00%
plugins/search/qssearch.py                     865      98  88.67%   382, 427-428, 545-546, 569-570, 582-586, 817-823, 881, 947, 977, 984, 1055, 1076, 1079-1080, 1098, 1107-1108, 1135, 1207, 1216, 1221-1238, 1247, 1262, 1271, 1284, 1306, 1402, 1425, 1440, 1513-1514, 1516-1518, 1520-1521, 1608, 1615, 1723-1727, 1798, 1801, 1805-1806, 1827-1830, 1842, 1864-1876, 1884, 1919-1921, 1944-1950, 1957, 2011, 2034, 2039-2040, 2055, 2061, 2075, 2176, 2180, 2191, 2215, 2228, 2236-2246
plugins/search/stac_list_assets.py              25      10  60.00%   44-51, 75-85
plugins/search/static_stac_search.py            84      18  78.57%   99-127, 166-169, 182, 224
resources/__init__.py                            0       0  100.00%
resources/shp/__init__.py                        0       0  100.00%
types/__init__.py                              180      43  76.11%   58, 71-75, 86-98, 126-128, 135-140, 155, 165, 247, 285, 295-311, 316, 318, 340, 345, 353, 363
types/bbox.py                                   39      19  51.28%   46-61, 72-74, 85-87, 99-101, 113-115, 123
types/download_args.py                          10       0  100.00%
types/queryables.py                            112       0  100.00%
types/search_args.py                            70      18  74.29%   60-64, 71-88, 103
types/stac_extensions.py                       148       4  97.30%   432-438, 619
types/stac_metadata.py                         154      22  85.71%   95, 117, 150, 183-185, 199-213, 222-229, 256-259
utils/__init__.py                              572      39  93.18%   60, 198, 229-230, 239-265, 268, 283, 363-367, 442-446, 526, 566-567, 596, 974-977, 1028, 1047-1048, 1077, 1095-1096, 1208, 1296, 1460, 1698
utils/cache.py                                  22       0  100.00%
utils/dates.py                                 182       4  97.80%   172, 516, 523-524
utils/deserialize.py                            73      10  86.30%   53, 55, 103, 108-109, 145, 159, 181-182, 188
utils/env.py                                     3       0  100.00%
utils/exceptions.py                             58       0  100.00%
utils/free_text_search.py                       65       2  96.92%   83, 91
utils/import_system.py                          28      19  32.14%   64-78, 89-99
utils/logging.py                                31       1  96.77%   41
utils/notebook.py                               44      23  47.73%   25-29, 36-41, 58-62, 72-78, 83-87
utils/repr.py                                   38       0  100.00%
utils/requests.py                               55      29  47.27%   51-52, 64, 85-96, 107-124, 128
utils/s3.py                                    240      12  95.00%   200-203, 245, 263, 489, 537-538, 585, 660, 686
utils/stac_reader.py                           113      44  61.06%   63-85, 95-97, 101, 138, 154-159, 206-216, 226-256
utils/streamresponse.py                         82       7  91.46%   24-26, 63, 124, 133, 145
TOTAL                                        11096    1341  87.91%

Diff against develop

Filename                                    Stmts    Miss  Cover
----------------------------------------  -------  ------  -------
api/product/_product.py                       +37       0  +0.84%
plugins/authentication/openid_connect.py       +8      +2  -0.55%
TOTAL                                         +45      +2  +0.03%

Results for commit: 9faf04a

Minimum allowed coverage is 70%

♻️ This comment has been updated with latest results

@eodag-bot
Copy link
Copy Markdown
Collaborator

eodag-bot commented Mar 25, 2026

badge

Code Coverage (Windows)

Details
Filename                                     Stmts    Miss  Cover    Missing
-----------------------------------------  -------  ------  -------  ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
__init__.py                                      8       0  100.00%
cli.py                                         251      11  95.62%   104-115, 377, 644
config.py                                      306      24  92.16%   70-72, 75, 78, 81, 85, 89, 93-95, 587-589, 711-713, 732, 740, 770-775, 777
crunch.py                                        2       0  100.00%
api/__init__.py                                  0       0  100.00%
api/collection.py                              151       8  94.70%   177, 213, 216, 323, 362, 365, 383, 386
api/core.py                                    774      57  92.64%   263, 549, 597, 640, 680, 700, 741-746, 771, 855-874, 888, 894, 1047, 1052, 1151, 1190-1191, 1287-1288, 1314, 1345-1346, 1372, 1385, 1446-1447, 1478-1479, 1553-1558, 1570-1573, 1685, 1910, 2067, 2178, 2266-2267
api/provider.py                                382      40  89.53%   186, 190-191, 316-337, 405, 521, 525-526, 531-534, 545, 621-631, 821-822, 870, 877, 891, 914-917, 951-958, 962-963
api/search_result.py                           138      12  91.30%   111, 123, 133, 154, 208, 266, 278, 413, 468-471
api/product/__init__.py                          7       0  100.00%
api/product/_assets.py                          54       4  92.59%   97, 191, 202-206
api/product/_product.py                        344      24  93.02%   208, 364, 390-393, 430, 441-442, 551, 580, 587, 626, 797, 840-843, 852-855, 909, 975, 987, 1054
api/product/metadata_mapping.py                806      60  92.56%   125-127, 221-226, 250, 308-309, 355-356, 397, 418, 470-471, 508, 529-532, 555, 567-568, 609, 632, 657-660, 662-667, 732-737, 746, 752, 768, 776, 1010, 1163, 1185, 1194-1198, 1215-1220, 1353, 1376, 1385, 1407, 1412, 1464, 1536, 1557, 1583, 1597, 1622, 1668, 1737, 1812
api/product/drivers/__init__.py                 11       0  100.00%
api/product/drivers/base.py                     32       0  100.00%
api/product/drivers/generic.py                  11       0  100.00%
api/product/drivers/sentinel1.py                33       0  100.00%
api/product/drivers/sentinel2.py                33       0  100.00%
plugins/__init__.py                              0       0  100.00%
plugins/base.py                                 25       2  92.00%   48, 55
plugins/manager.py                             173      16  90.75%   102-107, 179, 201, 219-220, 232, 271-272, 372-375, 387-388
plugins/apis/__init__.py                         0       0  100.00%
plugins/apis/base.py                             4       0  100.00%
plugins/apis/ecmwf.py                           95       8  91.58%   163-165, 213-214, 240-242
plugins/apis/usgs.py                           182      25  86.26%   157, 263, 297, 339-341, 346, 374-375, 380, 410-417, 428-433, 455-461
plugins/authentication/__init__.py               6       1  83.33%   31
plugins/authentication/aws_auth.py             124      35  71.77%   52-54, 69-70, 142-149, 177-203, 226, 258-262, 279, 303, 319-320
plugins/authentication/base.py                  22       4  81.82%   45, 58, 81, 95
plugins/authentication/eoiam.py                 99       2  97.98%   171, 194
plugins/authentication/generic.py               16       3  81.25%   50, 55, 65
plugins/authentication/header.py                19       0  100.00%
plugins/authentication/keycloak.py              46       4  91.30%   154, 177-182
plugins/authentication/openid_connect.py       243      21  91.36%   101-102, 110-128, 175, 183, 197, 217, 363-366, 392, 433, 623-626
plugins/authentication/qsauth.py                34       1  97.06%   91
plugins/authentication/sas_auth.py              57       3  94.74%   68, 89, 135
plugins/authentication/token.py                141       9  93.62%   192, 229, 309-310, 358-362
plugins/authentication/token_exchange.py        36      14  61.11%   75, 93-121
plugins/crunch/__init__.py                       6       0  100.00%
plugins/crunch/base.py                          12       0  100.00%
plugins/crunch/filter_date.py                   59       0  100.00%
plugins/crunch/filter_latest_intersect.py       54       6  88.89%   92-93, 100-101, 103-107
plugins/crunch/filter_latest_tpl_name.py        35       0  100.00%
plugins/crunch/filter_overlap.py                66      10  84.85%   118-121, 137-163
plugins/crunch/filter_property.py               30       0  100.00%
plugins/download/__init__.py                     4       0  100.00%
plugins/download/aws.py                        402      76  81.09%   271, 305, 354-357, 387-388, 396-400, 480-483, 523-525, 529, 560-561, 567-571, 602, 667-675, 739-834, 846-851, 889, 915, 960-962, 1014
plugins/download/base.py                       288      26  90.97%   135, 165, 232-234, 371-372, 416, 450, 527-531, 561, 596-597, 622-631, 690, 711, 733, 741
plugins/download/http.py                       575      74  87.13%   235, 277-280, 342-345, 348, 355-360, 391-393, 410, 425, 486, 521, 535, 549, 559-563, 579-584, 595, 614, 651-654, 675, 685, 692, 748, 857, 889, 921-930, 966, 1011-1016, 1025, 1040-1042, 1046, 1049, 1064-1065, 1075, 1150, 1202, 1244-1245, 1257, 1267, 1323-1324, 1354, 1392, 1472-1473
plugins/search/__init__.py                      25       0  100.00%
plugins/search/base.py                         197      18  90.86%   109, 113, 137-143, 218-221, 296, 317, 443, 493, 526-529, 538
plugins/search/build_search_result.py          519      91  82.47%   133-134, 170, 174, 194, 253, 423-434, 444-446, 532-538, 584, 608, 610, 677, 685-689, 710, 720, 746, 791, 816, 844, 862-877, 927, 952, 955, 959, 968, 974, 1012-1033, 1074, 1101-1102, 1111-1120, 1184, 1199, 1205, 1224-1233, 1354-1355, 1399, 1408-1410, 1467, 1515-1525
plugins/search/cop_ghsl.py                     407      85  79.12%   73-74, 109-110, 174, 176, 218, 249-252, 297, 313, 316, 350, 354-359, 374, 396, 417, 451, 467, 502, 517, 531-532, 546-548, 555, 559-561, 564-567, 585-645, 656-671
plugins/search/cop_marine.py                   273      59  78.39%   57, 65-67, 77-78, 83, 88-89, 105, 107, 110, 176-177, 220, 238, 244, 248, 252, 263, 274-275, 283, 315-318, 324, 334, 347, 351, 355, 359, 363-367, 373-376, 379-396, 413-416, 469-473, 478, 490, 504-505
plugins/search/creodias_s3.py                   29       1  96.55%   59
plugins/search/csw.py                          112      87  22.32%   99-100, 104-105, 113-170, 176-189, 197-229, 247-288
plugins/search/geodes.py                        45       0  100.00%
plugins/search/qssearch.py                     865      98  88.67%   382, 427-428, 545-546, 569-570, 582-586, 817-823, 881, 947, 977, 984, 1055, 1076, 1079-1080, 1098, 1107-1108, 1135, 1207, 1216, 1221-1238, 1247, 1262, 1271, 1284, 1306, 1402, 1425, 1440, 1513-1514, 1516-1518, 1520-1521, 1608, 1615, 1723-1727, 1798, 1801, 1805-1806, 1827-1830, 1842, 1864-1876, 1884, 1919-1921, 1944-1950, 1957, 2011, 2034, 2039-2040, 2055, 2061, 2075, 2176, 2180, 2191, 2215, 2228, 2236-2246
plugins/search/stac_list_assets.py              25      10  60.00%   44-51, 75-85
plugins/search/static_stac_search.py            84      18  78.57%   99-127, 166-169, 182, 224
resources/__init__.py                            0       0  100.00%
resources/shp/__init__.py                        0       0  100.00%
types/__init__.py                              180      43  76.11%   58, 71-75, 86-98, 126-128, 135-140, 155, 165, 247, 285, 295-311, 316, 318, 340, 345, 353, 363
types/bbox.py                                   39      19  51.28%   46-61, 72-74, 85-87, 99-101, 113-115, 123
types/download_args.py                          10       0  100.00%
types/queryables.py                            112       0  100.00%
types/search_args.py                            70      18  74.29%   60-64, 71-88, 103
types/stac_extensions.py                       148       4  97.30%   432-438, 619
types/stac_metadata.py                         154      22  85.71%   95, 117, 150, 183-185, 199-213, 222-229, 256-259
utils/__init__.py                              572      39  93.18%   60, 198, 229-230, 239-265, 268, 283, 363-367, 442-446, 526, 566-567, 596, 974-977, 1028, 1047-1048, 1077, 1095-1096, 1208, 1296, 1460, 1698
utils/cache.py                                  22       0  100.00%
utils/dates.py                                 182       4  97.80%   172, 516, 523-524
utils/deserialize.py                            73      10  86.30%   53, 55, 103, 108-109, 145, 159, 181-182, 188
utils/env.py                                     3       0  100.00%
utils/exceptions.py                             58       0  100.00%
utils/free_text_search.py                       65       2  96.92%   83, 91
utils/import_system.py                          28      19  32.14%   64-78, 89-99
utils/logging.py                                31       1  96.77%   41
utils/notebook.py                               44      23  47.73%   25-29, 36-41, 58-62, 72-78, 83-87
utils/repr.py                                   38       0  100.00%
utils/requests.py                               55      29  47.27%   51-52, 64, 85-96, 107-124, 128
utils/s3.py                                    240      12  95.00%   200-203, 245, 263, 489, 537-538, 585, 660, 686
utils/stac_reader.py                           113      44  61.06%   63-85, 95-97, 101, 138, 154-159, 206-216, 226-256
utils/streamresponse.py                         82       7  91.46%   24-26, 63, 124, 133, 145
TOTAL                                        11096    1343  87.90%

Diff against develop

Filename                                    Stmts    Miss  Cover
----------------------------------------  -------  ------  -------
api/product/_product.py                       +37       0  +0.84%
plugins/authentication/openid_connect.py       +8      +2  -0.55%
TOTAL                                         +45      +2  +0.03%

Results for commit: 9faf04a

Minimum allowed coverage is 70%

♻️ This comment has been updated with latest results

@cauriol cauriol marked this pull request as ready for review March 26, 2026 09:37
@cauriol cauriol requested a review from jlahovnik March 26, 2026 09:37
@cauriol cauriol marked this pull request as draft April 1, 2026 09:27
@cauriol cauriol removed the request for review from jlahovnik April 1, 2026 09:27
@cauriol cauriol changed the title refactor: CodeAuthorizedAuth headers handling for header/basic auth feat: Add Zarr v2 metadata access and basic auth support for OIDC-protected assets Apr 1, 2026
@alambare
Copy link
Copy Markdown
Collaborator

alambare commented Apr 1, 2026

@cauriol, instead of introducing a new method get_auth_headers in plugin configs, we should leverage the product._get_storage_options from EODAG cube: https://github.com/CS-SI/eodag-cube/blob/develop/eodag_cube/api/product/_product.py#L121.
This method should probably be refactored to not activate order and become a public interface.

@sbrunato can we get your opinion here ?

@sbrunato
Copy link
Copy Markdown
Collaborator

sbrunato commented Apr 2, 2026

@cauriol, instead of introducing a new method get_auth_headers in plugin configs, we should leverage the product._get_storage_options from EODAG cube: https://github.com/CS-SI/eodag-cube/blob/develop/eodag_cube/api/product/_product.py#L121. This method should probably be refactored to not activate order and become a public interface.

@sbrunato can we get your opinion here ?

@alambare I agree. EOProduct._get_storage_options can be moved from eodag-cube to eodag and publicly exposed

@cauriol cauriol force-pushed the auth_destinee branch 2 times, most recently from 141c247 to ca62273 Compare April 2, 2026 12:57
@cauriol cauriol marked this pull request as ready for review April 2, 2026 13:36
@cauriol cauriol requested a review from alambare April 2, 2026 13:36
Comment on lines +589 to +595
def request_asset(
self,
url: str,
) -> requests.Response:
"""Perform a GET request to the given URL using product's authentication headers."""
headers = self.get_storage_options().get("headers", {})
return requests.get(url, headers=headers, stream=True)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should use the stream download method from EODAG download plugins instead of creating a new method.

Comment thread eodag/api/product/_product.py Outdated
)

if ".zmetadata" in mapper:
meta = json.loads(mapper[".zmetadata"])
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use orjson instead of json.

Comment thread eodag/api/product/_product.py Outdated
Comment on lines +614 to +618
# TODO: Support Zarr v3 when test data becomes available.
# Zarr v2 uses `.zmetadata`, while Zarr v3 exposes `zarr.json`.
# The implementation should be straightforward once we can validate it
# against real examples.
raise ValueError(f"No Zarr metadata file found at {base_url}")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With Destination Earth, we do have a zarr v3 store: DanubeHis data. I don't understand what is blocking here.

Comment thread eodag/api/product/_product.py Outdated
headers = self.get_storage_options().get("headers", {})
return requests.get(url, headers=headers, stream=True)

def list_zarr_files_from_metadata(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In zarr, we have keys and not files. I suppose you can rename the method to something like list_zarr_keys ?

Comment thread eodag/api/product/_product.py Outdated
try:
url = self.assets[asset_key]["href"] if asset_key else self.location
except KeyError as e:
raise DatasetCreationError(f"{asset_key} not found in {self} assets") from e
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are not creating a xarray here. The exception class is not accurate.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put AddressNotFound now

Comment thread pyproject.toml Outdated
"zipstream-ng",
"fsspec",
"aiohttp",
"requests"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

requests is already a dependency in EODAG.

Comment thread eodag/api/product/_product.py Outdated
Comment on lines +605 to +608
mapper = fsspec.get_mapper(
base_url,
client_kwargs={"headers": headers, "trust_env": False},
)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure about introducing fsspec in EODAG library.
The library is a wrapper above multiple backends. To use it, you need to bring backends to support all the filesystems: s3fs for S3, gcsfs for google cloud storage and adlfs for azure blob storage.
Most of the times, users will store zarr files on a S3 compatible store but some may store them on google or azure stores.
Maybe we don't want to support google / azure stores ? or in an optional dependencies branch ?

My opinion:

  1. we do not support google and azure yet. Maybe once if we replace boto3 by obstore.
  2. we do not bring fsspec in EODAG but instead call boto3 to get the list of zarr keys. If a dedicated function is needed to get the list of keys with boto3, it should live in eodag/utils/s3.py.

@sbrunato what do you think?

Copy link
Copy Markdown
Collaborator

@sbrunato sbrunato Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think we should not import fsspec directly in eodag and also, not sure if we should handle zarr subsetting from eodag.

Having storage options is great and should be useful to open zarr from xarray or eodag-cube

@cauriol cauriol marked this pull request as draft April 10, 2026 12:10
@cauriol cauriol marked this pull request as ready for review April 13, 2026 07:46
@cauriol cauriol requested a review from alambare April 13, 2026 08:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants